<?php
/**
 * HTTP网络连接服务
 * 
 * $Id: http.php 128 2010-10-10 16:10:20Z Administrator $
 * $Revision: 128 $
 * $Author: Administrator $
 * $Date: 2010-10-11 00:10:20 +0800 (星期一, 11 十月 2010) $
 * @author glzaboy<glzaboy@163.com>
 * @package fastlib
 * @subpackage classes
 * @copyright 2005-2010 (c) glzaboy
 */
if (! defined ( '__FASTLIB__' )) {
	if (__DEBUG__) {
		die ( sprintf ( 'Forbidden 403 at file %s on line %d', __FILE__, __LINE__ ) );
	} else {
		die ( 'Forbidden 403' );
	}
}
/**
 * Http user-agent客户端模拟组件
 * 
 * 本类同时支持post,get,postfile,cookie的http
 * 协议发送和数据返回接收。
 * 本类使用http1.1进行发送。
 */
class http {
	/**
	 * 网络连接SOCKET
	 *
	 * @var object
	 */
	private $socket = null;
	/**
	 * 错误代码
	 *
	 * @var int
	 */
	private $errno = null;
	/**
	 * 错误文字说明
	 *
	 * @var string
	 */
	private $err = null;
	/**
	 * host主机名称
	 *
	 * @var string
	 */
	private $host = null;
	/**
	 * 超时时间
	 *
	 * @var int
	 */
	private $timeout = null;
	/**
	 * 端口
	 *
	 * @var int
	 */
	private $port = null;
	/**
	 * 记录日志对象
	 *
	 * @var logobject
	 */
	private $logObj = null;
	/**
	 * 请求中的COOKIE
	 *
	 * @var array
	 */
	public $cookie = array ();
	/**
	 * multipart/form-data
	 * 
	 *
	 * @var string;
	 */
	private $boundary = '';
	/**
	 * 服务器使用chunked传输
	 *
	 * @var bool
	 */
	private $chunked = false;
	/**
	 * Content-Encoding
	 *
	 * @var string
	 */
	private $zip = '';
	/**
	 * 路径数据
	 *
	 * @var array
	 */
	private $pathdeep = null;
	/**
	 * 构造函数
	 * 
	 * 服务器地址支持如下类型
	 * 127.0.0.1 
	 * fe80::1
	 * www.example.com
	 * tcp://127.0.0.1
	 * tcp://fe80::1
	 * tcp://www.example.com
	 * udp://www.example.com
	 * ssl://www.example.com
	 * sslv2://www.example.com
	 * sslv3://www.example.com
	 * tls://www.example.com 
	 * 具体支持情况和服务器配置有关，如果服务器禁用了fsocketopen本模块将不能用
	 * 
	 *
	 * @param string $host 服务器地址
	 * @param array $cookie cookie
	 * @param int $timeout 超时
	 * @param int $port 端口
	 */
	public function __construct($host, $cookie = array(), $timeout = 30, $port = 80) {
		$this->host = $host;
		$this->timeout = $timeout;
		$this->port = $port;
		$this->cookie = is_array ( $cookie ) ? $cookie : array ();
	}
	/**
	 * 关闭服务器注销本类 
	 *
	 */
	public function __destruct() {
		$this->disconnetc ();
	}
	/**
	 * 连接服务器
	 *
	 * @return bool 是否成功连接到服务器
	 */
	private function connetc() {
		if (is_resource ( $this->socket = fsockopen ( $this->host, $this->port, $this->errno, $this->err, $this->timeout ) )) {
			stream_set_timeout ( $this->socket, $this->timeout, 0 );
			return true;
		} else {
			return false;
		}
	}
	/**
	 * 关闭服务器连接
	 *
	 */
	private function disconnetc() {
		if (is_resource ( $this->socket )) {
			fclose ( $this->socket );
		}
	}
	/**
	 * 构造发送到服务器的文件数据
	 *
	 * @param array $data post 表单数组
	 * @param array $files 要编码的文件数组
	 * @return string 编码后的结果
	 */
	private function bind_multipartdata($data, $files) {
		$this->boundary = '--fastlib' . md5 ( uniqid ( microtime () ) );
		$postdata = '';
		if (is_array ( $data )) {
			reset ( $data );
			while ( false != (list ( $key, $val ) = each ( $data )) ) {
				if (is_array ( $val ) || is_object ( $val )) {
					while ( false != (list ( , $cur_val ) = each ( $val )) ) {
						$postdata .= '--' . $this->boundary . "\r\n";
						$postdata .= 'Content-Disposition: form-data; name="' . $key . '[]"' . "\r\n\r\n";
						$postdata .= $cur_val . "\r\n";
					}
				} else {
					$postdata .= '--' . $this->boundary . "\r\n";
					$postdata .= 'Content-Disposition: form-data; name="' . $key . '"' . "\r\n\r\n";
					$postdata .= $val . "\r\n";
				}
			}
		}
		if (is_array ( $files )) {
			reset ( $files );
			while ( false != (list ( $key, $val ) = each ( $files )) ) {
				if (is_array ( $val ) || is_object ( $val )) {
					while ( false != (list ( , $cur_val ) = each ( $val )) ) {
						if (strpos ( $cur_val, '://' )) {
							$type = substr ( $cur_val, 0, strpos ( $cur_val, '://' ) );
							$file = substr ( $cur_val, strpos ( $cur_val, '://' ) + 3 );
						} else {
							$type = 'application/octet-stream';
							$file = $cur_val;
						}
						if (! is_readable ( $file )) {
							continue;
						}
						$postdata .= '--' . $this->boundary . "\r\n";
						$postdata .= 'Content-Disposition: form-data; name="' . $key . '[]";filename="' . basename ( $file ) . '";' . "\r\n" . 'content-type:"' . $type . "\"\r\n\r\n";
						$postdata .= file_get_contents ( $file ) . "\r\n";
					}
				} else {
					if (strpos ( $val, '://' )) {
						$type = substr ( $val, 0, strpos ( $val, '://' ) );
						$file = substr ( $val, strpos ( $val, '://' ) + 3 );
					} else {
						$type = 'application/octet-stream';
						$file = $val;
					}
					if (! is_readable ( $file )) {
						continue;
					}
					$postdata .= '--' . $this->boundary . "\r\n";
					$postdata .= 'Content-Disposition: form-data; name="' . $key . '";filename="' . basename ( $file ) . '";' . "\r\n" . 'content-type:"' . $type . "\"\r\n\r\n";
					$postdata .= file_get_contents ( $file ) . "\r\n";
				}
			}
		}
		return $postdata . '--' . $this->boundary . '--' . "\r\n";
	}
	/**
	 * 构造服务器cookie
	 *
	 * @return string 发送到服务器的cookie字串
	 */
	private function bind_cookie() {
		if (is_array ( $this->cookie )) {
			$returnCookie = '';
			foreach ( $this->cookie as $k => $v ) {
				$returnCookie .= $k . '=' . $v . ';';
			}
		}
		return $returnCookie;
	}
	/**
	 * post表单文件到服务器
	 * 
	 * 上传文件使用如下所示写法
	 * array ('mp3' => 'image/gif://../head_01.gif' )
	 * 或
	 * array ('mp3' => '../head_01.gif' )
	 *
	 * @param string $url URL地址
	 * @param array $post post字段
	 * @param array $get get字段
	 * @param array $files 文件字段
	 * @param string $referer 引用地址
	 * @return string 服务器返回字串
	 */
	public function post($url, $post = null, $get = null, $files = null, $referer = null) {
		$this->pathdeep = explode ( '/', $url );
		if (! $this->connetc ()) {
			return false;
		}
		if (is_array ( $get )) {
			if (! strpos ( $url, '?' )) {
				$url .= '?';
			} else {
				$url .= '&';
			}
			$url .= strtr ( http_build_query ( $get ), array ('&amp;' => '&' ) );
		}
		if (false != ($cookie = $this->bind_cookie ())) {
			$cookie = 'Cookie: ' . $cookie;
		}
		if (! $referer) {
			$referer = 'http://' . $this->host;
		}
		if (is_array ( $files )) {
			$content = $this->bind_multipartdata ( $post, $files );
			$contentType = 'multipart/form-data; boundary=' . $this->boundary;
			$contentLen = strlen ( $content );
		} elseif (is_array ( $post )) {
			$contentType = 'application/x-www-form-urlencoded';
			$content = strtr ( http_build_query ( $post ), array ('&amp;' => '&' ) );
			$contentLen = strlen ( $content );
		} else {
			$contentType = 'application/x-www-form-urlencoded';
			$content = ( string ) $post;
			$contentLen = strlen ( $content );
		}
		$httpsend = 'POST ' . $url . ' HTTP/1.1' . "\r\n";
		$httpsend .= 'User-Agent: fastlib http user-agent ' . __FASTVER__ . ' (+http://www.codingspeed.com)' . "\r\n";
		if ($this->port == 80) {
			$httpsend .= 'Host: ' . $this->host . "\r\n";
		} else {
			$httpsend .= 'Host: ' . $this->host . ":" . $this->port . "\r\n";
		}
		$httpsend .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' . "\r\n";
		$httpsend .= 'Referer: ' . $referer . "\r\n";
		if ($cookie) {
			$httpsend .= $cookie . "\r\n";
		}
		$httpsend .= 'Content-type: ' . $contentType . "\r\n";
		$httpsend .= 'Content-length: ' . $contentLen . "\r\n";
		$httpsend .= 'Accept-Encoding: gzip,deflate' . "\r\n";
		$httpsend .= 'Connecion: close' . "\r\n";
		$httpsend .= "\r\n" . $content . "\r\n";
		$this->send ( $httpsend . "\r\n" );
		
		$recv = $this->recv ();
		$this->disconnetc ();
		return $recv;
	}
	/**
	 * get表单文件到服务器
	 *
	 * @param string $url URL地址
	 * @param array $get get字段
	 * @param string $referer 引用地址
	 * @return string 服务器返回字串
	 */
	public function get($url, $get = null, $referer = null) {
		$this->pathdeep = explode ( '/', $url );
		if (! $this->connetc ()) {
			return false;
		}
		if (is_array ( $get )) {
			if (! strpos ( $url, '?' )) {
				$url .= '?';
			} else {
				$url .= '&';
			}
			$url .= strtr ( http_build_query ( $get ), array ('&amp;' => '&' ) );
		}
		if (false != ($cookie = $this->bind_cookie ())) {
			$cookie = 'Cookie :' . $cookie;
		}
		if (! $referer) {
			$referer = 'http://' . $this->host;
		}
		$httpsend = 'GET ' . $url . ' HTTP/1.1' . "\r\n";
		$httpsend .= 'User-Agent: fastlib http user-agent ' . __FASTVER__ . ' (+http://www.codingspeed.com)' . "\r\n";
		if ($this->port == 80) {
			$httpsend .= 'Host: ' . $this->host . "\r\n";
		} else {
			$httpsend .= 'Host: ' . $this->host . ":" . $this->port . "\r\n";
		}
		$httpsend .= 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' . "\r\n";
		$httpsend .= 'Referer: ' . $referer . "\r\n";
		if ($cookie) {
			$httpsend .= $cookie . "\r\n";
		}
		$httpsend .= 'Accept-Encoding: gzip,deflate' . "\r\n";
		$httpsend .= 'Connecion: close' . "\r\n";
		$this->send ( $httpsend . "\r\n\r\n\r\n" );
		$recv = $this->recv ();
		$this->disconnetc ();
		return $recv;
	}
	/**
	 * 接收从服务器返回的数据
	 *
	 * @return mixed 服务器返回的数据
	 */
	private function recv() {
		if (! is_resource ( $this->socket )) {
			return false;
		}
		$this->chunked = false;
		//process header
		for(($httpheader = fgets ( $this->socket, 4096 )); $httpheader != "\r\n";) {
			if (false !== strpos ( strtolower ( $httpheader ), 'http/1.' )) { //status
				$status = substr ( $httpheader, 8, 5 );
				if ($status != 200) {
					$status = $httpheader;
				}
			}
			if (false !== strpos ( strtolower ( $httpheader ), 'location:' )) { //location:
				$url = trim ( substr ( $httpheader, 9 ) );
				if (substr ( $url, 0, 1 ) == '/') {
					return $this->get ( $url );
				} else {
					array_pop ( $this->pathdeep );
					$urlarr = explode ( '/', $url );
					foreach ( $urlarr as $v ) {
						if ($v == '..') {
							array_pop ( $this->pathdeep );
						} elseif ($v == '.') {
							continue;
						} else {
							array_push ( $this->pathdeep, $v );
						}
					}
					return $this->get ( implode ( '/', $this->pathdeep ) );
				}
			}
			if (false !== strpos ( strtolower ( $httpheader ), 'set-cookie' )) { //cookie
				$cookie = substr ( $httpheader, 11 );
				$cookiespit = explode ( ';', $cookie ); //include cookie,expires,path,domain,and etc.
				$cookienames = array ();
				foreach ( $cookiespit as $v ) {
					$cookieshift = explode ( '=', $v );
					$key = trim ( $cookieshift [0] );
					array_push ( $cookienames, $key );
					if (! in_array ( trim ( strtolower ( $key ) ), array ('path', 'domain' ) )) {
						$cookie = join ( "\n", array_slice ( $cookieshift, 1 ) ); //解码出来的cookie
						if (trim ( strtolower ( $key ) ) == 'expires') { //过期处理
							if (strtotime ( $cookie ) <= time ()) {
								foreach ( $cookienames as $cv ) {
									unset ( $this->cookie [$cv] );
								}
							}
						} else {
							$this->cookie [$key] = $cookie;
						}
					}
				}
			}
			if (false !== strpos ( strtolower ( $httpheader ), 'transfer-encoding:' )) { //chunked
				if (strtolower ( trim ( substr ( $httpheader, 18 ) ) ) == 'chunked') {
					$this->chunked = true;
				}
			}
			if (false !== strpos ( strtolower ( $httpheader ), 'content-encoding:' )) { //chunked
				$zip = strtolower ( trim ( substr ( $httpheader, 18 ) ) );
				if (in_array ( $zip, array ('gzip', 'deflate' ) )) {
					$this->zip = $zip;
				} else {
					$this->zip = '';
				}
			}
			$httpheader = fgets ( $this->socket, 4096 );
		}
		if ($status == 200) { //decode chunked
			$recv = '';
			if ($this->chunked) {
				$len = ( integer ) hexdec ( fgets ( $this->socket, 4096 ) );
				while ( ! feof ( $this->socket ) && $len > 0 ) {
					$recv .= fread ( $this->socket, $len );
					fread ( $this->socket, 2 );
					$len = ( integer ) hexdec ( fgets ( $this->socket, 4096 ) );
				}
			} else {
				while ( ! feof ( $this->socket ) ) {
					$recv .= fgets ( $this->socket );
				}
			}
			if ($this->zip == 'gzip') {
				return gzdecode ( $recv );
			} elseif ($this->zip == 'deflate') {
				return gzinflate ( $recv );
			}
			return $recv;
		} else {
			return $status;
		}
	}
	/**
	 * 发送数据到服务器
	 *
	 * @param mixed $data http编码后的数据
	 * @return 是否发送成功
	 */
	private function send($data) {
		if (is_resource ( $this->socket )) {
			$this->putlog ( $data, __FILE__, __LINE__ );
			return fputs ( $this->socket, $data, strlen ( $data ) );
		} else {
			return false;
		}
	}
	/**
	 * 记录日志
	 *
	 * @param String $message 日志内容
	 * @param String $file 调用文件名
	 * @param Int $line 调用文件行数
	 * @return bool 是否成功写入日志
	 */
	private function putlog($message, $file, $line) {
		if (! $this->logObj) {
			global $flibcfg;
			$this->logObj = new log ( $flibcfg ['logTimeFormat'], __CLASS__ . '.' . $flibcfg ['logFileName'] );
		}
		return $this->logObj->putlog ( $message, $file, $line );
	}
	/**
	 * 返回本模块信息
	 *
	 * @return string
	 */
	public function printInfo() {
		$version = '$Revision: 128 $';
		$moudle = __CLASS__;
		$return = '';
		$return .= startTable ();
		$return .= printRow ( array (array ('class' => 'h', 'content' => '<center>' . $moudle . '</center>', 'colspan' => '2' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'author' ), array ('class' => 'v', 'content' => 'glzaboy' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'version' ), array ('class' => 'v', 'content' => $version ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'support' ), array ('class' => 'v', 'content' => 'post,get,post file,cookie and automatic decode chunked,gzip,deflate,location' ) ) );
		$return .= printRow ( array (array ('class' => 'e', 'content' => 'Dependence' ), array ('class' => 'v', 'content' => 'fsocketopen' ) ) );
		$return .= endTable ();
		return $return;
	}
}